package luj.tool.jnproxyan.proxy.network.socksv2.server.handler;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.socksx.v5.DefaultSocks5CommandResponse;
import io.netty.handler.codec.socksx.v5.Socks5CommandRequest;
import io.netty.handler.codec.socksx.v5.Socks5CommandRequestDecoder;
import io.netty.handler.codec.socksx.v5.Socks5CommandStatus;
import io.netty.handler.codec.socksx.v5.Socks5CommandType;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import luj.cluster.api.actor.Tellable;
import luj.tool.jnproxyan.framework.lujnet.ConnState;
import luj.tool.jnproxyan.proxy.network.https.client.SslLogHandler;
import luj.tool.jnproxyan.proxy.network.https.keystore.CertDynamicIssuer;
import luj.tool.jnproxyan.proxy.network.https.keystore.KeystoreFileReader;
import luj.tool.jnproxyan.proxy.network.logger.Client2pLogger;
import luj.tool.jnproxyan.proxy.network.socks.actor.root.message.request.connect.ReqConnectMsg;
import luj.tool.jnproxyan.proxy.network.socksv2.connection.ConnStateAttr;
import luj.tool.jnproxyan.framework.proxy.actor.tell.ActorTeller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;

import static com.google.common.base.Preconditions.checkState;

@Component
public class S5CommandReqHandler extends SimpleChannelInboundHandler<Socks5CommandRequest> {

  public S5CommandReqHandler inject(Tellable socksRef) {
    var result = new S5CommandReqHandler();
    result._socksRef = socksRef;

    result._receiveHttpHandler = _receiveHttpHandler;
    result._actorTeller = _actorTeller;

    return result;
  }

  // https://www.ietf.org/rfc/rfc1928.txt
  // 6.  Replies
  @Override
  protected void channelRead0(ChannelHandlerContext ctx, Socks5CommandRequest msg) throws Exception {
    checkState(msg.type().equals(Socks5CommandType.CONNECT));

    var rsp = new DefaultSocks5CommandResponse(Socks5CommandStatus.SUCCESS, msg.dstAddrType());
    ctx.writeAndFlush(rsp).sync();

    ConnState c2pConn = new ConnStateAttr(ctx.channel()).get();
    Client2pLogger c2pLog = new Client2pLogger(LOG, c2pConn.getReqId());
    c2pLog.log("建立{}：{}", msg.version(), msg.dstAddr());

    String targetHost = msg.dstAddr();
    if (targetHost.contains("google")) {
      c2pLog.log("无视连接请求：{}:{}", targetHost, msg.dstPort());
      return;
    }

    // 回复浏览器后，就准备ssl连接建立
    prepareSsl(ctx, msg);

    // 同时请求远端网站
    requestConnect(msg, c2pConn);
  }

  /**
   * @see ReceiveHttpReqHandler#channelRead0
   */
  private void prepareSsl(ChannelHandlerContext ctx, Socks5CommandRequest req) throws Exception {
    ChannelPipeline pipeline = ctx.pipeline();
    pipeline.remove(this);

    SslContext sslCtx = makeSslContext(req.dstAddr());
    pipeline.replace(Socks5CommandRequestDecoder.class, null, sslCtx.newHandler(ctx.alloc()));
    pipeline.addLast(new SslLogHandler());
    pipeline.addLast(new HttpServerCodec());
//    pipeline.addLast(new LoggingHandler());

    //TODO: 这里收到浏览器的网页请求，应该先发到actor管理的队列里
//    pipeline.addLast(new HttpTestHandler());
    pipeline.addLast(_receiveHttpHandler.inject(_socksRef));
  }

  private SslContext makeSslContext(String addr) throws Exception {
    KeyStore keystore = new KeystoreFileReader().read();
    var caPriv = (PrivateKey) keystore.getKey("ca", "abc123".toCharArray());
    var caCert = (X509Certificate) keystore.getCertificate("ca");

    var priv = (PrivateKey) keystore.getKey("dynamic", "abc123".toCharArray());
    var dynamicCert = (X509Certificate) keystore.getCertificate("dynamic");
    X509Certificate cert = new CertDynamicIssuer(caPriv, caCert, dynamicCert.getPublicKey(), addr).issue();

    return SslContextBuilder.forServer(priv, cert).build();
  }

  /**
   * @see luj.tool.jnproxyan.proxy.network.socks.actor.root.message.request.connect.OnReqConnect#onHandle
   */
  private void requestConnect(Socks5CommandRequest cmd, ConnState c2pConn) {
    Long reqId = c2pConn.getReqId();
    String targetHost = cmd.dstAddr();
    int targetPort = cmd.dstPort();
    new Client2pLogger(LOG, reqId).log("<浏>请求连：{}:{}", targetHost, targetPort);

    _actorTeller.inject(_socksRef).tell(ReqConnectMsg.class, (b, m) -> b
        .f(m::version).set(cmd.version())
        .f(m::cmd).set(cmd.type())
        .f(m::targetHost).set(targetHost)
        .f(m::targetPort).set(targetPort)
        .f(m::reqId).set(reqId)
        .f(m::methods).set(c2pConn.getMethods())
    );
  }

  private static final Logger LOG = LoggerFactory.getLogger(S5CommandReqHandler.class);

  Tellable _socksRef;

  @Autowired
  ReceiveHttpReqHandler _receiveHttpHandler;

  @Autowired
  ActorTeller _actorTeller;
}
